﻿using System;
using System.IO;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Text;
using System.Timers;
using System.Windows.Threading;
using uTorrentWebAPI.ServiceDefinition;
using Timer = System.Windows.Forms.Timer;

namespace uTorrentWebAPI
{
    /// <summary>
    ///     Represents an instance of uTorrent.
    ///     Uses the Web UI API to connect to uTorrent to retrieve and update information.
    /// </summary>
    public class UTorrentWebClient : IDisposable
    {
        #region Private Fields

        private readonly bool _autoUpdateAsynchronous;
        private readonly Dispatcher _dispatcher;
        private bool _autoUpdate;
        private System.Timers.Timer _autoUpdateTimerAsync;
        private Timer _autoUpdateTimerSync;

        private long _cacheID;
        private WebChannelFactory<IUTorrentWebClient> _channelFactory;
        private TorrentLabelCollection _labels;
        private TimeSpan _minimumTimeBetweenUpdates;

        private IUTorrentWebClient _serviceClient;

        private SettingsCollection _settings;
        private DateTime _settingsLastUpdated;
        private TorrentCollection _torrents;
        private DateTime _torrentsAndLabelsLastUpdated;
        private bool _torrentsAndLabelsListStored;
        private string _uTorrentAddress;
        private string _uTorrentPassword;
        private string _uTorrentUserName;

        #endregion

        #region Constructors

        /// <summary>
        ///     Create a new UTorrentWebClient with no auto updating
        /// </summary>
        /// <param name="serverAddress">
        ///     The address of the uTorrent Web UI. This can be expressed as a hostname (e.g.
        ///     utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and
        ///     a suffix '/gui/'. If these elements are not included, they will be added.
        /// </param>
        /// <param name="userName">The username used to access the uTorrent Web UI.</param>
        /// <param name="password">The password used to access the uTorrent Web UI.</param>
        public UTorrentWebClient(string serverAddress, string userName, string password)
            : this(serverAddress, userName, password, false)
        {
        }

        /// <summary>
        ///     Create a new UTorrentWebClient with optional auto updating
        /// </summary>
        /// <param name="serverAddress">
        ///     The address of the uTorrent Web UI. This can be expressed as a hostname (e.g.
        ///     utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and
        ///     a suffix '/gui/'. If these elements are not included, they will be added.
        /// </param>
        /// <param name="userName">The username used to access the uTorrent Web UI.</param>
        /// <param name="password">The password used to access the uTorrent Web UI.</param>
        /// <param name="autoUpdate">Whether or not to auto update the UTorrentWebClient object automatically</param>
        public UTorrentWebClient(string serverAddress, string userName, string password, bool autoUpdate)
            : this(serverAddress, userName, password, autoUpdate, null)
        {
        }

        /// <summary>
        ///     Create a new UTorrentWebClient with optional auto updating and optionally passing a
        ///     System.Windows.Threading.Dispatcher to handle UI events
        /// </summary>
        /// <param name="serverAddress">
        ///     The address of the uTorrent Web UI. This can be expressed as a hostname (e.g.
        ///     utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and
        ///     a suffix '/gui/'. If these elements are not included, they will be added.
        /// </param>
        /// <param name="userName">The username used to access the uTorrent Web UI.</param>
        /// <param name="password">The password used to access the uTorrent Web UI.</param>
        /// <param name="autoUpdate">Whether or not to auto update the UTorrentWebClient object automatically</param>
        /// <param name="dispatcher">A System.Windows.Threading.Dispatcher to handle UI events</param>
        public UTorrentWebClient(string serverAddress, string userName, string password, bool autoUpdate,
            Dispatcher dispatcher)
        {
            _autoUpdateAsynchronous = true;
            MinimumTimeBetweenUpdates = TimeSpan.FromSeconds(2);
            _dispatcher = dispatcher;
            this.AutoUpdate = autoUpdate;

            SetConnectionDetails(serverAddress, userName, password);
        }

        /// <summary>
        ///     Change the connection details used to connect to the uTorrent Web UI
        /// </summary>
        /// <param name="serverAddress">
        ///     The address of the uTorrent Web UI. This can be expressed as a hostname (e.g.
        ///     utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and
        ///     a suffix '/gui/'. If these elements are not included, they will be added.
        /// </param>
        /// <param name="userName">The username used to access the uTorrent Web UI.</param>
        /// <param name="password">The password used to access the uTorrent Web UI.</param>
        public void SetConnectionDetails(string serverAddress, string userName, string password)
        {
            _torrentsAndLabelsListStored = false;

            _uTorrentAddress = ProcessServerAddress(serverAddress);
            _uTorrentUserName = userName;
            _uTorrentPassword = password;

            var uTorrentCustomBinding = new CustomBinding(
                new WebMessageEncodingBindingElement {ContentTypeMapper = new JsonContentTypeMapper()},
                new HttpTransportBindingElement
                {
                    ManualAddressing = true,
                    AuthenticationScheme = AuthenticationSchemes.Basic,
                    Realm = "uTorrent",
                    AllowCookies = true,
                    MaxReceivedMessageSize = 1048576
                }
                );
            var uTorrentEndpointAddress = new EndpointAddress(_uTorrentAddress);

            _channelFactory = new WebChannelFactory<IUTorrentWebClient>(uTorrentCustomBinding);
            _channelFactory.Endpoint.Address = uTorrentEndpointAddress;
            _channelFactory.Credentials.UserName.UserName = _uTorrentUserName;
            _channelFactory.Credentials.UserName.Password = _uTorrentPassword;
            _serviceClient = _channelFactory.CreateChannel();
            Token = RetrieveToken();
        }

        #endregion

        #region Internal Properties

        internal Dispatcher Dispatcher
        {
            get { return _dispatcher; }
        }

        internal string Token { get; set; }

        #endregion

        #region Public Properties

        /// <summary>
        ///     Whether or not to automatically update the list of torrents and labels.
        ///     If true, the collections of torrents and labels will be updated at intervals of MinimumTimeBetweenUpdates
        /// </summary>
        public bool AutoUpdate
        {
            get { return _autoUpdate; }
            set
            {
                _autoUpdate = value;
                if (_autoUpdate)
                {
                    if (_autoUpdateAsynchronous)
                    {
                        if (_autoUpdateTimerAsync == null)
                        {
                            _autoUpdateTimerAsync = new System.Timers.Timer(MinimumTimeBetweenUpdates.TotalMilliseconds);
                            _autoUpdateTimerAsync.Elapsed += AutoUpdateTimer_Elapsed;
                        }
                        else
                        {
                            _autoUpdateTimerAsync.Interval = MinimumTimeBetweenUpdates.TotalMilliseconds;
                        }
                        _autoUpdateTimerAsync.Start();
                    }
                    else
                    {
                        if (_autoUpdateTimerSync == null)
                        {
                            _autoUpdateTimerSync = new Timer();
                            _autoUpdateTimerSync.Interval = Convert.ToInt32(MinimumTimeBetweenUpdates.TotalMilliseconds);
                            _autoUpdateTimerSync.Tick += AutoUpdateTimer_Tick;
                        }
                        else
                        {
                            _autoUpdateTimerSync.Interval = Convert.ToInt32(MinimumTimeBetweenUpdates.TotalMilliseconds);
                        }
                        _autoUpdateTimerSync.Start();
                    }
                }
                else
                {
                    if (_autoUpdateTimerSync != null)
                    {
                        _autoUpdateTimerSync.Stop();
                    }
                    if (_autoUpdateTimerAsync != null)
                    {
                        _autoUpdateTimerAsync.Stop();
                    }
                }
            }
        }

        /// <summary>
        ///     The minimum time between updates or the time between automatic updates.
        ///     If information is requested from an object that gets its information from uTorrent, and the information is less
        ///     than MinimumTimeBetweenUpdates old, then the cached information will be returned.
        /// </summary>
        public TimeSpan MinimumTimeBetweenUpdates
        {
            get { return _minimumTimeBetweenUpdates; }
            set
            {
                _minimumTimeBetweenUpdates = value;
                if (AutoUpdate)
                {
                    if (_autoUpdateTimerSync != null)
                    {
                        _autoUpdateTimerSync.Interval = Convert.ToInt32(_minimumTimeBetweenUpdates.TotalMilliseconds);
                    }
                    if (_autoUpdateTimerAsync != null)
                    {
                        _autoUpdateTimerAsync.Interval = _minimumTimeBetweenUpdates.TotalMilliseconds;
                    }
                }
            }
        }

        /// <summary>
        ///     The collection of torrents currently managed by uTorrent
        /// </summary>
        public TorrentCollection Torrents
        {
            get
            {
                GetTorrentsAndLabels();
                return _torrents;
            }
        }

        /// <summary>
        ///     The collection of torrent labels currently managed by uTorrent
        /// </summary>
        public TorrentLabelCollection Labels
        {
            get
            {
                GetTorrentsAndLabels();
                return _labels;
            }
        }

        /// <summary>
        ///     The collection of uTorrent settings
        /// </summary>
        public SettingsCollection Settings
        {
            get
            {
                UpdateSettings();
                return _settings;
            }
        }

        private void AutoUpdateTimer_Tick(object sender, EventArgs e)
        {
            GetTorrentsAndLabels(true);
        }

        private void AutoUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            GetTorrentsAndLabels(true);
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///     Starts a torrent
        /// </summary>
        /// <param name="torrent">The torrent to start</param>
        public void TorrentStart(Torrent torrent)
        {
            _serviceClient.StartTorrent(torrent.Hash, Token);
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Stops a torrent
        /// </summary>
        /// <param name="torrent">The torrent to stop</param>
        public void TorrentStop(Torrent torrent)
        {
            _serviceClient.StopTorrent(torrent.Hash, Token);
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Pauses a torrent
        /// </summary>
        /// <param name="torrent">The torrent to pause</param>
        public void TorrentPause(Torrent torrent)
        {
            _serviceClient.PauseTorrent(torrent.Hash, Token);
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Un-pauses a torrent
        /// </summary>
        /// <param name="torrent">The torrent to un-pause</param>
        public void TorrentUnPause(Torrent torrent)
        {
            _serviceClient.UnPauseTorrent(torrent.Hash, Token);
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Force starts a torrent
        /// </summary>
        /// <param name="torrent">The torrent to force start</param>
        public void TorrentForceStart(Torrent torrent)
        {
            _serviceClient.ForceStartTorrent(torrent.Hash, Token);
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Re-checks a torrent
        /// </summary>
        /// <param name="torrent">The torrent to re-check</param>
        public void TorrentReCheck(Torrent torrent)
        {
            _serviceClient.RecheckTorrent(torrent.Hash, Token);
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Removes a torrent from uTorrent
        /// </summary>
        /// <param name="torrent">The torrent to remove</param>
        /// <param name="removeData">Whether or not the data is removed</param>
        public void TorrentRemove(Torrent torrent, bool removeData)
        {
            if (removeData)
            {
                _serviceClient.RemoveTorrentAndData(torrent.Hash, Token);
            }
            else
            {
                _serviceClient.RemoveTorrent(torrent.Hash, Token);
            }
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Removes a torrent from uTorrent and keeps the data
        /// </summary>
        /// <param name="torrent">The torrent to remove</param>
        public void TorrentRemove(Torrent torrent)
        {
            TorrentRemove(torrent, false);
        }

        /// <summary>
        ///     Adds a torrent to uTorrent from a url
        /// </summary>
        /// <param name="url">The url to download the .torrent file</param>
        public void AddTorrentFromUrl(string url)
        {
            _serviceClient.AddTorrentFromUrl(url, Token);
            GetTorrentsAndLabels(true);
        }

        /// <summary>
        ///     Adds a torrent to uTorrent from a file
        /// </summary>
        /// <param name="fileName">The name of the file to add</param>
        public void AddTorrent(string fileName)
        {
            AddTorrent(File.OpenRead(fileName), fileName);
        }

        /// <summary>
        ///     Adds a torrent to uTorrent from a stream
        /// </summary>
        /// <param name="torrentStream">The stream containing the torrent to add</param>
        public void AddTorrent(Stream torrentStream)
        {
            AddTorrent(torrentStream, "C:\\Torrent.torrent");
        }

        /// <summary>
        ///     Adds a torrent to uTorrent from a stream, uploading it with a specified file name
        /// </summary>
        /// <param name="torrentStream">The stream containing the torrent to add</param>
        /// <param name="fileName">The filename to upload the torrent using</param>
        public void AddTorrent(Stream torrentStream, string fileName)
        {
            var uTorrentCredentials = new CredentialCache();
            uTorrentCredentials.Add(new Uri(_uTorrentAddress), "Basic",
                new NetworkCredential(_uTorrentUserName, _uTorrentPassword));
            var postFileRequest =
                (HttpWebRequest) (WebRequest.Create(String.Format("{0}?action=add-file", _uTorrentAddress)));
            postFileRequest.KeepAlive = false;
            postFileRequest.Credentials = uTorrentCredentials;
            string boundaryString = Guid.NewGuid().ToString("N");
            postFileRequest.ContentType = "multipart/form-data; boundary=" + boundaryString;
            postFileRequest.Method = "POST";

            using (var writer = new BinaryWriter(postFileRequest.GetRequestStream()))
            {
                var fileBytes = new byte[torrentStream.Length];
                torrentStream.Read(fileBytes, 0, fileBytes.Length);

                writer.Write(
                    Encoding.ASCII.GetBytes(
                        String.Format(
                            "--{0}\r\nContent-Disposition: form-data; name=\"torrent_file\"; filename=\"{0}\"\r\nContent-Type: application/x-bittorrent\r\n\r\n",
                            boundaryString, fileName)));
                writer.Write(fileBytes, 0, fileBytes.Length);
                writer.Write(Encoding.ASCII.GetBytes(String.Format("\r\n--{0}--\r\n", boundaryString)));
            }

            var response = (HttpWebResponse) postFileRequest.GetResponse();
            response.Close();
            postFileRequest.Abort();
            GetTorrentsAndLabels(true);
        }

        #region IDisposable implementation

        public void Dispose()
        {
            _serviceClient = null;
            _channelFactory.Close();
            _channelFactory = null;
        }

        #endregion

        #endregion

        #region Helper Methods

        private string ProcessServerAddress(string serverAddress)
        {
            string serverAddressProcessed = serverAddress.ToLowerInvariant();
            if (!serverAddressProcessed.StartsWith("http://"))
            {
                serverAddressProcessed = String.Format("http://{0}", serverAddressProcessed);
            }
            if (!serverAddressProcessed.EndsWith("/"))
            {
                serverAddressProcessed = String.Format("{0}/", serverAddressProcessed);
            }
            if (!serverAddressProcessed.EndsWith("/gui/"))
            {
                serverAddressProcessed = String.Format("{0}gui/", serverAddressProcessed);
            }
            return serverAddressProcessed;
        }

        private string RetrieveToken()
        {
            html token = _serviceClient.Authenticate();

            return token.div.token;
        }

        #endregion

        #region Public Events

        /// <summary>
        ///     Called when a torrent enters the started state
        /// </summary>
        public event TorrentEventHandler TorrentStarted;

        /// <summary>
        ///     Called when a torrent enters the stopped state
        /// </summary>
        public event TorrentEventHandler TorrentStopped;

        /// <summary>
        ///     Called when a torrent is paused
        /// </summary>
        public event TorrentEventHandler TorrentPaused;

        /// <summary>
        ///     Called when a torrent is unpaused
        /// </summary>
        public event TorrentEventHandler TorrentUnPaused;

        /// <summary>
        ///     Called when a torrent has finished downloading
        /// </summary>
        public event TorrentEventHandler TorrentFinishedDownloading;

        /// <summary>
        ///     Called when a new torrent is added
        /// </summary>
        public event TorrentEventHandler TorrentAdded;

        /// <summary>
        ///     Called when the list of torrents is updated
        /// </summary>
        public event EventHandler TorrentsUpdated;

        internal void CallEvent(EventType type, Torrent torrent)
        {
            CallEvent(type, torrent, false);
        }

        internal void CallEvent(EventType type, Torrent torrent, bool fromDispatcher)
        {
            if (!fromDispatcher && _dispatcher != null)
            {
                _dispatcher.BeginInvoke(new CallEventDelegate(CallEvent), DispatcherPriority.Loaded,
                    new object[] {type, torrent, true});
            }
            else
            {
                switch (type)
                {
                    case EventType.Started:
                        if (TorrentStarted != null)
                        {
                            TorrentStarted(this, new TorrentEventArgs(torrent));
                        }
                        break;
                    case EventType.Stopped:
                        if (TorrentStopped != null)
                        {
                            TorrentStopped(this, new TorrentEventArgs(torrent));
                        }
                        break;
                    case EventType.Paused:
                        if (TorrentPaused != null)
                        {
                            TorrentPaused(this, new TorrentEventArgs(torrent));
                        }
                        break;
                    case EventType.UnPaused:
                        if (TorrentUnPaused != null)
                        {
                            TorrentUnPaused(this, new TorrentEventArgs(torrent));
                        }
                        break;
                    case EventType.FinishedDownloading:
                        if (TorrentFinishedDownloading != null)
                        {
                            TorrentFinishedDownloading(this, new TorrentEventArgs(torrent));
                        }
                        break;
                    case EventType.Added:
                        if (TorrentAdded != null)
                        {
                            TorrentAdded(this, new TorrentEventArgs(torrent));
                        }
                        break;
                    case EventType.Updated:
                        if (TorrentsUpdated != null)
                        {
                            TorrentsUpdated(this, new EventArgs());
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        private delegate void CallEventDelegate(EventType type, Torrent torrent, bool fromDispatcher);

        internal enum EventType
        {
            Started,
            Stopped,
            Paused,
            UnPaused,
            FinishedDownloading,
            Added,
            Updated
        }

        #endregion

        #region Getting Torrents And Labels

        private void GetTorrentsAndLabels()
        {
            GetTorrentsAndLabels(false);
        }

        private void GetTorrentsAndLabels(bool ForceUpdate)
        {
            if (_torrentsAndLabelsLastUpdated == null ||
                _torrentsAndLabelsLastUpdated.Add(MinimumTimeBetweenUpdates) <= DateTime.Now || ForceUpdate)
            {
                if (_torrentsAndLabelsListStored && _cacheID > 0)
                {
                    GetTorrentsAndLabelsCached();
                }
                else
                {
                    if (_torrentsAndLabelsListStored)
                    {
                        GetTorrentsAndLabelsUpdate();
                    }
                    else
                    {
                        GetTorrentsAndLabelsFresh();
                    }
                }
                CallEvent(EventType.Updated, null);
            }
        }

        private void GetTorrentsAndLabelsCached()
        {
            UpdatedTorrentsAndLabels updatedTorrents = _serviceClient.GetUpdatedTorrentsAndLabels(_cacheID.ToString(),
                Token);

            _torrents.Parse(updatedTorrents.Torrents, updatedTorrents.RemovedTorrents, updatedTorrents.ChangedTorrents);
            _labels.Parse(updatedTorrents.Labels);
            SetCache(updatedTorrents.CacheID);
        }

        private void GetTorrentsAndLabelsUpdate()
        {
            GetTorrentsAndLabelsUpdate(null);
        }

        private void GetTorrentsAndLabelsUpdate(TorrentsList torrentsToProcess)
        {
            TorrentsAndLabels currentTorrents = _serviceClient.GetAllTorrentsAndLabels(Token);

            _torrents.Parse(currentTorrents.Torrents, false);
            _labels.Parse(currentTorrents.Labels);
            SetCache(currentTorrents.CacheID);
        }

        private void GetTorrentsAndLabelsFresh()
        {
            TorrentsAndLabels currentTorrents = _serviceClient.GetAllTorrentsAndLabels(Token);

            _torrents = new TorrentCollection(this);
            _torrents.Parse(currentTorrents.Torrents, true);

            _labels = new TorrentLabelCollection();
            _labels.Parse(currentTorrents.Labels);

            SetCache(currentTorrents.CacheID);
        }

        private void SetCache(string cacheIdString)
        {
            long parsedCacheId = 0;
            if (long.TryParse(cacheIdString, out parsedCacheId) && parsedCacheId > 0)
            {
                _cacheID = parsedCacheId;
            }
            else
            {
                _cacheID = 0;
            }
            _torrentsAndLabelsListStored = true;
            _torrentsAndLabelsLastUpdated = DateTime.Now;
        }

        #endregion

        #region Getting Settings

        private void UpdateSettings()
        {
            UpdateSettings(false);
        }

        private void UpdateSettings(bool forceUpdate)
        {
            if (_settingsLastUpdated == null || _settingsLastUpdated.Add(MinimumTimeBetweenUpdates) < DateTime.Now ||
                forceUpdate)
            {
                UTorrentSettings currentSettings = _serviceClient.GetSettings(Token);
                if (_settings == null)
                {
                    _settings = new SettingsCollection();
                }
                _settings.ParseSettings(currentSettings.Settings, this);
                _settingsLastUpdated = DateTime.Now;
            }
        }

        internal void SetSetting(SettingBase setting)
        {
            switch (setting.Type)
            {
                case SettingType.Integer:
                    _serviceClient.SetIntegerSetting(setting.Name, ((SettingInteger) setting).Value, Token);
                    break;
                case SettingType.String:
                    _serviceClient.SetStringSetting(setting.Name, ((SettingString) setting).Value, Token);
                    break;
                case SettingType.Boolean:
                    _serviceClient.SetBooleanSetting(setting.Name, ((SettingBoolean) setting).Value ? "true" : "false",
                        Token);
                    break;
                default:
                    break;
            }
            UpdateSettings(true);
        }

        #endregion

        #region Getting And Setting Torrent Files

        internal void UpdateTorrentFiles(Torrent torrentToUpdate, bool ForceUpdate)
        {
            if (torrentToUpdate.FilesLastUpdated == null ||
                torrentToUpdate.FilesLastUpdated.Add(MinimumTimeBetweenUpdates) < DateTime.Now || ForceUpdate)
            {
                if (torrentToUpdate.FileList == null)
                {
                    torrentToUpdate.FileList = new TorrentFileCollection(torrentToUpdate);
                }
                torrentToUpdate.FileList.ParseFiles(_serviceClient.GetFiles(torrentToUpdate.Hash, Token).Files,
                    torrentToUpdate);
                torrentToUpdate.FilesLastUpdated = DateTime.Now;
            }
        }

        internal void UpdateTorrentFiles(Torrent torrentToUpdate)
        {
            UpdateTorrentFiles(torrentToUpdate, false);
        }

        internal void SetTorrentFilePriority(TorrentFile FileToUpdate, int Priority)
        {
            _serviceClient.SetFilePriority(
                FileToUpdate.ParentCollection.ParentTorrent.Hash,
                FileToUpdate.Index,
                Priority,
                Token);
            UpdateTorrentFiles(FileToUpdate.ParentCollection.ParentTorrent, true);
        }

        #endregion

        #region Getting And Setting Torrent Properties

        internal void UpdateTorrentProperties(Torrent torrentToUpdate, bool forceUpdate)
        {
            if (torrentToUpdate.PropertiesLastUpdated == null ||
                torrentToUpdate.PropertiesLastUpdated.Add(MinimumTimeBetweenUpdates) < DateTime.Now || forceUpdate)
            {
                TorrentPropertiesList[] currentProperties =
                    _serviceClient.GetProperties(torrentToUpdate.Hash, Token).Properties;
                if (currentProperties.Length != 1)
                {
                    throw new FormatException("The array of torrent properties was not in the expected format.");
                }
                torrentToUpdate.ParseProperties(currentProperties[0]);
                torrentToUpdate.PropertiesLastUpdated = DateTime.Now;
            }
        }

        internal void UpdateTorrentProperties(Torrent torrentToUpdate)
        {
            UpdateTorrentProperties(torrentToUpdate, false);
        }

        internal void SetTorrentProperty(Torrent torrentToUpdate, string propertyName, int propertyValue)
        {
            _serviceClient.SetIntegerProperty(torrentToUpdate.Hash, propertyName, propertyValue, Token);
        }

        internal void SetTorrentProperty(Torrent torrentToUpdate, string propertyName, long propertyValue)
        {
            _serviceClient.SetLongProperty(torrentToUpdate.Hash, propertyName, propertyValue, Token);
        }

        internal void SetTorrentProperty(Torrent torrentToUpdate, string propertyName, string propertyValue)
        {
            _serviceClient.SetStringProperty(torrentToUpdate.Hash, propertyName, propertyValue, Token);
        }

        #endregion
    }

    #region Torrent Events

    /// <summary>
    ///     Event arguments for a torrent event
    /// </summary>
    public class TorrentEventArgs : EventArgs
    {
        private readonly Torrent _torrent;

        internal TorrentEventArgs(Torrent torrent)
        {
            _torrent = torrent;
        }

        /// <summary>
        ///     The torrent to which the event is referring
        /// </summary>
        public Torrent Torrent
        {
            get { return _torrent; }
        }
    }

    public delegate void TorrentEventHandler(object sender, TorrentEventArgs e);

    #endregion
}